package drds.server.buffer;

import java.nio.ByteBuffer;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;

import drds.server.util.ByteBufferUtil;

/**
 * used for large data write ,composed by buffer array, when a large MySQL
 * package write ,shoud use this object to write data
 * 
 * use DirectByteBuffer for alloc buffer
 * 
 * @author 曹正辉<330937205@qq.com>
 * 
 */
public class ByteBufferList {
	private static byte[] EMPTY = new byte[0];
	private final ByteBufferPool byteBufferPool;
	private ByteBuffer writingByteBuffer;
	private List<ByteBuffer> writedByteBufferList = Collections.emptyList();

	public ByteBufferList(ByteBufferPool byteBufferPool) {
		super();
		this.byteBufferPool = byteBufferPool;
		this.writingByteBuffer = byteBufferPool.allocate(byteBufferPool.getBlockSize());
	}

	public ByteBuffer checkWritingByteBufferCapacity(int capacity) {
		if (capacity > this.writingByteBuffer.remaining()) {
			addToWritedByteBufferList(this.writingByteBuffer);
			this.writingByteBuffer = byteBufferPool.allocate(capacity);
			return this.writingByteBuffer;
		} else {
			return this.writingByteBuffer;
		}
	}

	public int getBlockCount() {
		return writedByteBufferList.size() + 1;
	}

	private void addToWritedByteBufferList(ByteBuffer byteBuffer) {
		if (writedByteBufferList.isEmpty()) {
			writedByteBufferList = new LinkedList<ByteBuffer>();
		}
		writedByteBufferList.add(byteBuffer);
	}

	public ByteBuffer getWritingByteBuffer() {
		return writingByteBuffer;
	}

	public List<ByteBuffer> getWritedByteBufferList() {
		return writedByteBufferList;
	}

	public void clear() {
		writingByteBuffer = null;
		writedByteBufferList.clear();
		writedByteBufferList = null;
	}

	public ByteBuffer write(byte[] bytes) {
		int offset = 0;
		int remains = bytes.length;
		while (remains > 0) {
			int writeable = writingByteBuffer.remaining();
			/**
			 * can write whole data
			 */
			if (writeable >= remains) {
				writingByteBuffer.put(bytes, offset, remains);
				break;
			} else {
				/**
				 * only can write partly
				 */
				writingByteBuffer.put(bytes, offset, writeable);
				offset += writeable;
				remains -= writeable;
				addToWritedByteBufferList(writingByteBuffer);
				writingByteBuffer = byteBufferPool.allocate(byteBufferPool.getBlockSize());
				continue;
			}
		}
		return writingByteBuffer;
	}

	public byte[] writeToByteArrayAndRecycle() {
		ByteBufferList byteBufferList = this;
		try {

			int size = 0;
			{
				List<ByteBuffer> writedByteBufferList = byteBufferList.getWritedByteBufferList();
				if (!byteBufferList.getWritedByteBufferList().isEmpty()) {
					for (ByteBuffer byteBuffer : writedByteBufferList) {
						byteBuffer.flip();
						size += byteBuffer.remaining();
					}
				}
				ByteBuffer byteBuffer = byteBufferList.getWritingByteBuffer();
				byteBuffer.flip();
				if (byteBuffer.hasRemaining()) {
					size += byteBuffer.remaining();
				}
			}
			if (size > 0) {
				int offset = 0;
				byte[] bytes = new byte[size];
				if (!byteBufferList.getWritedByteBufferList().isEmpty()) {
					for (ByteBuffer byteBuffer : writedByteBufferList) {

						ByteBufferUtil.arrayCopy(byteBuffer, 0, bytes, offset, byteBuffer.remaining());
						offset += byteBuffer.remaining();
						byteBufferPool.recycle(byteBuffer);
					}
				}
				ByteBuffer byteBuffer = byteBufferList.getWritingByteBuffer();
				if (byteBuffer.hasRemaining()) {
					ByteBufferUtil.arrayCopy(byteBuffer, 0, bytes, offset, byteBuffer.remaining());
					byteBufferPool.recycle(byteBuffer);
				}
				return bytes;
			}

		} finally {

			byteBufferList.clear();
		}

		return EMPTY;
	}

}
